MySQL 分離レベル
#mysql #db
概要
MySQLのInnoDBでは、トランザクション分離レベルをどのように実現してるのか。
また、トランザクション分離レベルにMVCCをどう絡ませてるのか。
などなどをしっかり言語化しておく。
説明
MySQLにおける4つの分離レベルそれぞれの表現・仕様を説明していく。
基本的に分離レベルの動きは、自トランザクション(以降Tx)がどのレベルかによって変わる。
レベル別の仕様
REPEATABLE READ
InnoDBのデフォルト分離レベル。
Tx内のノンロックリード(= Consistent reads)は、最初の読み取りによって確立されたsnapshotを読む。
(注意:トランザクション開始時点ではなく、最初の読み取り時点でのスナップショット)
以降、同Tx内で同じクエリを叩いた場合は、最初と同じ結果が返される。
Tx内のロックリード(= locking reads)は、ステートメントが一意の検索条件を持つ一意のインデックスを使用するか、範囲タイプの検索条件を使用するかによって異なる
一意の検索条件を使用した一意のインデックスの場合
InnoDB は見つかったインデックスレコードのみをロックし、その前にあるギャップはロックしません。
その他の検索条件の場合
InnoDB は、ギャップロックまたはネクストキーロックを使用して、範囲に含まれるギャップへのほかのセッションによる挿入をブロックすることによって、スキャンされたインデックス範囲をロックします。
READ COMMITTED
REPEATABLE READとは異なり、同じTx内でもステートメントごとにスナップショットを取る。
そもそもスナップショットとはなんぞや?
a.icon 以下
クエリーには、その時点よりも前にコミットされたトランザクションによる変更のみが表示され、その時点よりもあとのトランザクションまたはコミットされていないトランザクションによる変更は表示されません。
つまり、その時点でコミットが完了してる状態を保存する。コミットされてない状態は保存しない。
この読み取りごとにスナップショットを取ることで、ダーティリードを防いでる。
ロックリードの場合は、たとえ範囲検索だとしてもギャップロックは取らない。
理由:READ COMMITTEDは、ファントムリードOKだから。
特例
READ COMMITTEDはギャップロックが無効で、更新・削除対象になるレコードに対してのみロックをかける。
もし、レコードが存在しない場合は、すぐにInnoDBに解除される。
後もう一つ以下
UPDATE ステートメントである行がすでにロックされていた場合、InnoDB は 「半一貫性」 読み取りを実行し、最後にコミットされたバージョンを MySQL に返すため、MySQL はその行が UPDATE の WHERE 条件に一致するかどうかを判断できます。 その行が一致した場合 (その行を更新する必要がある場合)、MySQL はその行を再度読み取り、InnoDB は今度はその行をロックするか、その行のロックが解除されるまで待機します。
READ COMMITTEDの場合、例えインデックスレコード無しのスキャンをしたとしても、全レコードに対してロックをかけることはない。
READ UNCOMMITTED
あまり言うことなし。
SERIALIZABLE
このレベルは REPEATABLE READ と似ていますが、autocommit が無効になっている場合、InnoDB はすべてのプレーン SELECT ステートメントを SELECT ... FOR SHARE に暗黙的に変換します。 autocommit が有効な場合、SELECT は独自のトランザクションです。 したがって、読み取り専用であることがわかっているため、一貫性のある (非ロック) 読み取りとして実行された場合は直列化することができ、ほかのトランザクションのためのブロックは必要ありません。 (選択した行が他のトランザクションによって変更された場合にプレーン SELECT を強制的にブロックするには、autocommit を無効にします。)
参考
InnoDBの分離レベルによるMySQLのパフォーマンスへの影響 | Yakst
DBのロックについてあまり意識したことがない人に向けた実は覚えておきたいロックについての知識 - CARTA TECH BLOG